#include <assert.h>
#include <math.h>
#include "port.h"
#include <stdio.h>
#include "common.h"
#include "background.h"
#include "sprite.h"
#include "databank.h"

// Definition of shared variable
int Zone::resolution = 1;

void Zone::calculateCenter(BITMAP *b)
{
    int numobj;

    // Clear the old
    center.pos[0] = 0.0;
    center.pos[1] = 0.0;
    center.mass = 0.0;

    numobj = 0;
    int magicPink = makecol(255,0,255);
    for(int y = pos[1]; y < pos[1]+resolution && y < b->h; y++) {
        for(int x = pos[0]; x < pos[0]+resolution && x < b->w; x++) {
            if(_getpixel32(b,x,y) != magicPink) { // Assumes 32-bit color
                numobj+=1;
                center.mass += PARTICLE_MASS;

                // Add to position
                center.pos[0] += double(x)*PARTICLE_MASS;
                center.pos[1] += double(y)*PARTICLE_MASS;
            }
        }
    }

    // Divide by number of elements to get center of mass
    if(numobj != 0) {
        center.pos[0] /= double(numobj)*PARTICLE_MASS;
        center.pos[1] /= double(numobj)*PARTICLE_MASS;
        
        // Assume it is full if ~2/3 filled
        if(numobj >= int(double(resolution*resolution)*0.66))
        	full = true;
        else full = false;
    }else {
        center.pos[0] = pos[0]+resolution/2;
        center.pos[1] = pos[1]+resolution/2;
        
        full = false;
    }

    updated = 1;
}

void Background::clearParticle(int x,int y)
{ freezeParticle(x,y,magicPink); }

void Background::freezeParticle(int x, int y, int col)
{
    // Magic pink is a special case
    int color,r=0,g=0,b=0;
    int n;
    n = 1;
    if(col != magicPink && cfg->blurParticles == true) {
        // Stronger bias against the real color
        putpixel(background, x,y, col);

        // Use neighbor pixels for color
        for(int ix=x-1;ix<=x+1;ix++) {
            for(int iy=y-1;iy<=y+1;iy++) {
                if(is_inside_bitmap(background,ix,iy,0)) {
                    color = getpixel(background,ix,iy);
                    if(color != magicPink) {
                        n++;

                        r += getr(color);
                        g += getg(color);
                        b += getb(color);
                    }
                }
            }
        }
    }

    color = col;
    r += getr(color);
    g += getg(color);
    b += getb(color);

    int res;
    res = makecol(r/n,g/n,b/n);
    putpixel(background, x,y, res);

    // Mark a zone as dirty
    int zx,zy;
    zx = x/zoneMap->getResolution();
    zy = y/zoneMap->getResolution();

    if(zx >= zoneMap->getW() || zx < 0 ||
            zy >= zoneMap->getH() || zy < 0) {
        // HELP!
    }else { // Safe to get
        Zone *z;
        z = zoneMap->getZone(zx,zy);
        z->updated = 0; // Cry for an update!
    }
}

void Background::init()
{

    cfg = data->getconfig();

    background = create_bitmap(cfg->screenWidth, cfg->screenHeight);

    // Prepare zonemap
    zoneMap = new ZoneMap(cfg->screenWidth/cfg->physPartitionRes,
            cfg->screenHeight/cfg->physPartitionRes, cfg->physPartitionRes);

    // Make zones
    for(int x=0; x < cfg->screenWidth/cfg->physPartitionRes; x++) {
        for(int y=0; y < cfg->screenHeight/cfg->physPartitionRes; y++) {
            class Zone *tmp;
            tmp = new class Zone;
            
            tmp->setResolution(cfg->physPartitionRes);
            tmp->pos[0] = x*cfg->physPartitionRes;
            tmp->pos[1] = y*cfg->physPartitionRes;

            // Duplicated system o_O
            zoneMap->setZone(x,y,tmp);
        }
    }


    // Update all mass center zones
    updateAll();
}

void Background::generateMap()
{
    // Magic pink to the rescue
    clear_to_color(background, makecol(255,0,255));

    for (int i=0; i<cfg->numPlanets; ++i) {
        int x = cfg->screenWidth/4+rand()%(cfg->screenWidth/2);
        int y = cfg->screenHeight/4+rand()%(cfg->screenHeight/2);
        draw_sprite(background, data->sprites->get_random_planet(), x-64, y-64);
    }
}

void Background::newGame()
{
    // Update zonemap
    updateAll();
}

void Background::clear()
{
	// Nothing to do?
}

ZoneMap::ZoneMap(int w,int h, int resolution)
{
    this->w = w;
    this->h = h;
    this->resolution = resolution;

    zmap = new Zone **[this->h];
    for(int y=0; y<h; y++)
        zmap[y] = new Zone *[this->w];
}

void Background::updateZones()
{
    int hasChanged;

    hasChanged = 0;
    for(int y=0; y<zoneMap->getH(); y++) {
        for(int x=0; x<zoneMap->getW(); x++) {
            Zone *z = zoneMap->getZone(x,y);
            if(z->updated == 0) {
                hasChanged = 1;
                z->calculateCenter(background);
            }
        }
    }

    // Regenerate the active zones
    if(hasChanged) {
        zones.clear();
        int numOpts = 0;
		
		// Run through all zones and compress them if they're full
		if(cfg->optimizePhysics) {
			double massSum,compSum;
			
			massSum = 0.0;
			compSum = 0.0;
			for(int y=0; y<zoneMap->getH(); y++) {
				for(int x=0; x<zoneMap->getW(); x++) {
					Zone *mz = zoneMap->getZone(x,y);
					massSum += mz->getCenter()->mass;
				}
			}
			
			for(int y=0; y<zoneMap->getH(); y++) {
				for(int x=0; x<zoneMap->getW(); x++) {
					Zone *mz = zoneMap->getZone(x,y);
					
					if(mz->full) {
						// Assemble a new centre and clear the surrounding (full) centres
						for(int iy= ((y-1>=0) ? y-1: 0) ; iy<=y+1 && iy < zoneMap->getH(); iy++) {
							for(int ix= ((x-1>=0) ? x-1: 0) ; ix<=x+1 && ix < zoneMap->getW(); ix++) {
								// Do not include the centre point
								if(iy==y && ix==x) continue;
								
								Zone *z = zoneMap->getZone(ix,iy);
								
								if(z->full) {
									// Clear it and push it into the master zone
									numOpts++;
									
									mz->getCenter()->mass += z->getCenter()->mass;
									mz->getCenter()->pos[0] += z->getCenter()->pos[0]/mz->getCenter()->mass;
									mz->getCenter()->pos[1] += z->getCenter()->pos[1]/mz->getCenter()->mass;
									z->full = false;
									
									z->getCenter()->mass = 0;
								}
							}
						}
					} // mz->full
				} // zoneMap->getW
			} // zoneMap->getH
		} // optimizePhysics

        for(int y=0; y<zoneMap->getH(); y++) {
            for(int x=0; x<zoneMap->getW(); x++) {
                Zone *z = zoneMap->getZone(x,y);

                // If more than virtually massless
                if(z->getCenter()->mass >= 1.0e-5) {
                    zones.push_back(z);
                }
            }
        }
        //printf("Zones: %d (optimized: %d)\n", zones.size(), numOpts);
    }

    return;
}

void Background::updateAll()
{
    for(int y=0; y<zoneMap->getH(); y++) {
        for(int x=0; x<zoneMap->getW(); x++) {
            Zone *z = zoneMap->getZone(x,y);
            z->updated = 0;
        }
    }

    updateZones();
}


// Explosion functions
// Using BITMAP* as pointer to Background? Evil evil evil!
// It was TOO evil!
void Background::explodeParticle(int x,int y)
{
    // Prevent out-of-bounds
    if(!is_inside_bitmap(background,x,y,0)) {
        return;
    }

    int partColor;
    partColor = getpixel(background, x,y);
    if(partColor == magicPink) return;

    clearParticle(x,y); // Handles zone updating

    // Make the particle
    double pos[2];
    double vel[2];
    pos[0] = x;
    pos[1] = y;

    // Velocity
    double xsign,ysign,length,speed,dir,power;
    #define SIGN(x) ((x)<0.0 ? -1.0 : ((x)>0.0 ? 1.0 : 0.0))
    xsign = SIGN(exVel[0]);//xsign = (exVel[0] < 0.0 ? -1.0: 1.0;
    ysign = SIGN(exVel[1]);//ysign = exVel[1] <= 0.0 ? -1.0: 1.0;
    length = sqrt((centPos[0]-pos[0])*(centPos[0]-pos[0]) +
            (centPos[1]-pos[1])*(centPos[1]-pos[1]));
    speed = sqrt(exVel[0]*exVel[0]+exVel[1]*exVel[1]);
    power = -2.5*sqrt(exRadius)*exPower*sqrt(length);
    
    // Three parts to an explosion
    // - Explosion away from the centre of explosion
    // - Explosion towards the ideal direction out from the planet
    // - Explosion based on the velocity of impact
    dir = (centPos[0]-pos[0])/length; // Directon from the centre to this position, as a percentage of length
    vel[0] = power*dir																* (0.8+frand()*0.4);
    vel[0]+=-exPower*exDir[0]*sqrt(exRadius)*4.0 /sqrt(length)						* (0.8+frand()*0.4);
    vel[0]+= xsign*speed/12.0														* (0.8+frand()*0.4);
    
    dir = (centPos[1]-pos[1])/length; // Directon from the centre to this position, as a percentage of length
    vel[1] = power*dir																* (0.8+frand()*0.4);
    vel[1]+=-exPower*exDir[1]*sqrt(exRadius)*4.0 /sqrt(length)						* (0.8+frand()*0.4);
    vel[1]+= ysign*speed/12.0														* (0.8+frand()*0.4);
	
    // Make particle
    data->makeParticle(pos,vel, partColor);

    return;
}

void Background::calculateEscape(int cx,int cy, double radius)
{
    int rad = int(radius);
    int count = 0;
    double dir[2];

	// Always include initial point
    dir[0] = 0.0;//cx;//double(testCollision(cx,cy))*0.0;
    dir[1] = 0.0;//cy;//double(testCollision(cx,cy))*0.0;
    count = 0;

    // Loop through the circle and gather points
    if(testCollision(cx,cy)) count++;
    for(int x=1;x<rad; x++) {
        double f;

        double ex = double(x)/radius;
        f = sqrt(1.0-ex*ex)*radius;

        if (testCollision(cx+x,cy)) {
            count++;
            dir[0] += double(x);
            dir[1] += double(0.0);
        }
        
        if (testCollision(cx-x,cy)) {
            count++;
            dir[0] += double(-x);
            dir[1] += double(0.0);
        }

        for(int y=1;y<int(f);y++) {
            if(testCollision(cx+x,cy+y)) {
                count++;
                dir[0] += double(+x);
                dir[1] += double(+y);
            }

            if(testCollision(cx+x,cy-y)) {
                count++;
                dir[0] += double(+x);
                dir[1] += double(-y);
            }

            if(testCollision(cx-x,cy+y)) {
                count++;
                dir[0] += double(-x);
                dir[1] += double(+y);
            }
            if(testCollision(cx-x,cy-y)) {
                count++;
                dir[0] += double(-x);
                dir[1] += double(-y);
            }
        }
    }

    exDir[0] = (dir[0])/double(count);//-double(cx);
    exDir[1] = (dir[1])/double(count);//-double(cy);
}

void Background::explodeBackground(int pos[2], double vel[2], double radius, double power)
{
    centPos[0] = pos[0];
    centPos[1] = pos[1];
    exVel[0] = vel[0];
    exVel[1] = vel[1];
    exRadius = radius;
    exPower = power;
    calculateEscape(pos[0],pos[1],radius);

    //do_circle(background, pos[0],pos[1], radius, 0, explodeParticle);
    // Four corners of a circle:
    // Circle function for a quadrant sqrt(1-x^2)
    // Scaled for radius (in 1 pixel steps):
    // sqrt(radius^2-x^2) [0,radius]
    for(int x=0;x<int(radius); x++) {
        double f;

        double ex = double(x)/radius;
        f = sqrt(1.0-ex*ex)*radius;

        for(int y=0;y<int(f);y++) {
            explodeParticle(pos[0]+x,pos[1]+y);
            explodeParticle(pos[0]+x,pos[1]-y);
            explodeParticle(pos[0]-x,pos[1]+y);
            explodeParticle(pos[0]-x,pos[1]-y);
        }
    }

    // HAVE to update the zonemap after an explosion.. unfortunately
    updateZones();

    return;
}

void Background::findLines(int x,int y)
{
	int left;
	int right;
	int color,u_prev,d_prev;
	
	// For all lines up and down
	if(getpixel(background,x,y) == magicPink) return;
	
	// Always add above and below line [if they're usable]
	if(y > 0 && testCollision(x,y-1)) {
		lines.push_back(LinePointer(x,y-1));
	}
	
	if(y+1 < background->h && testCollision(x,y+1)) {
		lines.push_back(LinePointer(x,y+1));
	}
	
	u_prev = getpixel(background,x,y-1);
	d_prev = getpixel(background,x,y+1);
	for(left = x-1; left >= 0 && testCollision(left,y); left--) {
		color = getpixel(background,left,y-1);
		if(y>0 && color != magicPink && u_prev == magicPink) {
			lines.push_back(LinePointer(left,y-1));
		} // Add to edges if there's nothing here
		else if(y>=0 && color == magicPink) {
			edges.push_back(LinePointer(left,y));
		}
		u_prev = color;

		color = getpixel(background,left,y+1);
		if(y+1< background->h && color != magicPink && d_prev == magicPink) {
			lines.push_back(LinePointer(left,y+1));
		}
		else if(y<background->h && color == magicPink) {
			edges.push_back(LinePointer(left,y));
		}
		d_prev = color;
	}
	
	u_prev = getpixel(background,x,y-1);
	d_prev = getpixel(background,x,y+1);
	for(right = x+1; right < background->w && testCollision(right,y); right++) {
		color = getpixel(background,right,y-1);
		if(y>0 && color != magicPink && u_prev == magicPink) {
			lines.push_back(LinePointer(right,y-1));
		}
		else if(y>=0 && color == magicPink) {
			edges.push_back(LinePointer(right,y));
		}
		u_prev = color;

		color = getpixel(background,right,y+1);
		if(y+1< background->h && color != magicPink && d_prev == magicPink) {
			lines.push_back(LinePointer(right,y+1));
		}
		else if(y<background->h && color == magicPink) {
			edges.push_back(LinePointer(right,y));
		}
		d_prev = color;
	}
}

bool Background::searchEdge(int x,int y)
{
	int tol = 4;
	bool found = false;
	
	// Test x-axis
	for(int ix = x-tol; ix<=x+tol; ix++) {
		if(ix >= 0 && ix < background->w) {
			if(testCollision(ix,y)) {
				lines.push_back(LinePointer(ix,y));
				found = true;
				break;
			}
		}
	}
	
	// Test y-axis
	for(int iy = y-tol; iy<=y+tol; iy++) {
		if(iy >= 0 && iy < background->h) {
			if(testCollision(x,iy)) {
				lines.push_back(LinePointer(x,iy));
				found = true;
				break;
			}
		}
	}
	
	return found;
}

void Background::traceLine(int x,int y)
{
	int left,right;
	
	if(y < 0 || y >= background->h) return;

    double ang,str;
    double pos[2];
    double vel[2];
    
    pos[1] = y;
    for(left = x; left >= 0 && testCollision(left, y); left--) {
    	// Random velocity for particle
		ang = frand()*PIE*2.0;
		str = frand()*exPower;
		vel[0] = cos(ang)*str;
		vel[1] =-sin(ang)*str;
		
    	pos[0] = left;
    	
		data->makeParticle(pos,vel,
		        getpixel(background, left,y));
		clearParticle(left,y);
    }
    
    for(right = x+1; right < background->w && testCollision(right,y); right++) {
    	// Random velocity for particle
		ang = frand()*M_PI*2.0;
		str = frand()*exPower;
		vel[0] = cos(ang)*str;
		vel[1] =-sin(ang)*str;
		
    	pos[0] = right;
    	
		data->makeParticle(pos,vel,
		        getpixel(background, right,y));
		clearParticle(right,y);
    }
	recCount++;
}

void Background::explodeFillInternal(int x,int y)
{
	findLines(x,y);
	traceLine(x,y);

	do {
		// Trace lines
		for(std::list<LinePointer>::iterator i = lines.begin(); i != lines.end(); i++) {
			LinePointer ptr(0,0);
			ptr = (*i);
			findLines(ptr.x,ptr.y);
			traceLine(ptr.x,ptr.y);
		}
		printf("Lines: %d\n", lines.size());
		printf("Edges: %d\n", edges.size());
		lines.clear();
		
		// Search edges
		std::list<LinePointer>::iterator cur;
		for(cur = edges.begin(); cur != edges.end(); cur++) {
			if(searchEdge((*cur).x, (*cur).y))
				break;
		}
		if(cur != edges.end()) {
			cur++;
			edges.erase(edges.begin(), cur);
		}else edges.clear();
	}while(lines.size()>0);
}

void Background::explodeFill(int x,int y,double power)
{
	exPower = power;
	recCount = 0;
	
	explodeFillInternal(x,y);
	
    updateZones();
}

